/*
 *  Copyright (c) 2021 Otávio Santana and others
 *   All rights reserved. This program and the accompanying materials
 *   are made available under the terms of the Eclipse Public License v1.0
 *   and Apache License v2.0 which accompanies this distribution.
 *   The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html
 *   and the Apache License v2.0 is available at http://www.opensource.org/licenses/apache2.0.php.
 *
 *   You may elect to redistribute this code under either of these licenses.
 *
 *   Contributors:
 *
 *   Otavio Santana
 */
package {{package}};

import jakarta.data.Order;
import jakarta.data.Sort;
import jakarta.data.page.Page;
import jakarta.data.page.PageRequest;
import org.eclipse.jnosql.communication.semistructured.SelectQuery;
import org.eclipse.jnosql.lite.mapping.metadata.LiteEntitiesMetadata;
import org.eclipse.jnosql.mapping.core.NoSQLPage;
import org.eclipse.jnosql.mapping.metadata.EntitiesMetadata;
import org.eclipse.jnosql.mapping.metadata.EntityMetadata;
import org.eclipse.jnosql.mapping.metadata.FieldMetadata;
import org.eclipse.jnosql.mapping.semistructured.MappingQuery;

import javax.annotation.processing.Generated;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream;
import org.eclipse.jnosql.mapping.Database;
import org.eclipse.jnosql.mapping.DatabaseType;
import org.eclipse.jnosql.mapping.semistructured.SemiStructuredTemplate;

import static java.util.Objects.nonNull;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.toList;
import static java.util.stream.StreamSupport.stream;
import static org.eclipse.jnosql.mapping.IdNotFoundException.KEY_NOT_FOUND_EXCEPTION_SUPPLIER;

@Generated(value= "JNoSQL Lite {{providerType}} Repository Generator", date = "{{now}}")
@jakarta.enterprise.context.ApplicationScoped
@jakarta.enterprise.inject.Default
@Database(value = DatabaseType.{{providerType}})
public class {{className}} implements {{repository}} {

    {{^methods.isEmpty}}
    private static final org.eclipse.jnosql.communication.semistructured.SelectQueryParser SELECT_PARSER =
            new org.eclipse.jnosql.communication.semistructured.SelectQueryParser();
    private static final org.eclipse.jnosql.communication.semistructured.DeleteQueryParser DELETE_PARSER =
            new org.eclipse.jnosql.communication.semistructured.DeleteQueryParser();
    {{/methods.isEmpty}}

    private final SemiStructuredTemplate template;

    private final EntityMetadata metadata;

    @jakarta.inject.Inject
    public {{className}}(@Database(value = DatabaseType.{{providerType}}) SemiStructuredTemplate template) {
        this.template = Objects.requireNonNull(template, "template is required");
        EntitiesMetadata entities = LiteEntitiesMetadata.INSTANCE;
        this.metadata = entities.get({{entityType}}.class);
    }

    public {{className}}() {
        this.template = null;
        this.metadata = null;
   }

   SemiStructuredTemplate template(){
        return template;
    }

    EntityMetadata entityMetadata(){
        return metadata;
    }

    @Override
    public <S extends {{entityType}}> S save(S entity) {
        Objects.requireNonNull(entity, "Entity is required");
        Object id = getIdField().read(entity);
        if (nonNull(id) && existsByIdJNoSQL(({{keyType}}) id)) {
            return template().update(entity);
        } else {
            return template().insert(entity);
        }
    }

    @Override
    public <S extends {{entityType}}> List<S> saveAll(List<S> entities) {
        requireNonNull(entities, "entities is required");
        return stream(entities.spliterator(), false).map(this::save).collect(toList());
    }

    @Override
    public void deleteById({{keyType}} id) {
        requireNonNull(id, "is is required");
        template().delete(getEntityClass(), id);
    }

    @Override
    public void delete({{entityType}} entity) {
        requireNonNull(entity, "entity is required");
        Object id = getIdField().read(entity);
        this.deleteById(({{keyType}}) id);
    }

    @Override
    public Stream<{{entityType}}> findAll() {
        SelectQuery query = SelectQuery.select().from(entityMetadata().name()).build();
        return template().select(query);
    }

    @Override
    public void deleteAll(List<? extends {{entityType}}> entities) {
       requireNonNull(entities, "entities is required");
       entities.forEach(this::delete);
   }


    @Override
    public Page<{{entityType}}> findAll(PageRequest pageRequest, Order<{{entityType}}> order) {
        Objects.requireNonNull(pageRequest, "pageRequest is required");
        EntityMetadata metadata = entityMetadata();
        List<Sort<?>> sorts = order.sorts().stream().collect(toList());
        SelectQuery query = new MappingQuery(sorts,
        pageRequest.size(), NoSQLPage.skip(pageRequest), null ,metadata.name(), java.util.Collections.emptyList());

        List<{{entityType}}> entities = template().<{{entityType}}>select(query).toList();
        return NoSQLPage.of(entities, pageRequest);
    }

   @Override
   public Optional<{{entityType}}> findById({{keyType}} id) {
       requireNonNull(id, "id is required");
       return template().find(getEntityClass(), id);
   }


    {{#element.isNoSQLRepository}}

   @Override
   public boolean existsById({{keyType}} id) {
       return findById(id).isPresent();
   }


    @Override
    public void deleteAll() {
        template().deleteAll(getEntityClass());
    }

    @Override
    public void deleteByIdIn(Iterable<{{keyType}}> ids) {
        requireNonNull(ids, "ids is required");
        ids.forEach(this::deleteById);
    }

   @Override
   public long countBy() {
       return template().count(getEntityClass());
   }

    @Override
    public Stream<{{entityType}}> findByIdIn(Iterable<{{keyType}}> ids) {
        requireNonNull(ids, "ids is required");
        return stream(ids.spliterator(), false)
        .flatMap(optionalToStream());
    }
    {{/element.isNoSQLRepository}}

    {{#element.isCrudRepository}}
    @Override
    public <S extends {{entityType}}> S insert(S entity) {
        Objects.requireNonNull(entity, "entity is required");
        return template().insert(entity);
    }
 
    @Override
    public <S extends {{entityType}}> List<S> insertAll(List<S> entities) {
        Objects.requireNonNull(entities, "entities is required");
        return stream(template().insert(entities).spliterator(), false).toList();
    }

    @Override
    public {{entityType}} update({{entityType}} entity) {
        Objects.requireNonNull(entity, "entity is required");
        return template().update(entity);
    }

    @Override
    public <S extends {{entityType}}> List<S> updateAll(List<S> entities) {
       Objects.requireNonNull(entities, "entities is required");
       return stream(template().update(entities).spliterator(), false).toList();
    }

    {{/element.isCrudRepository}}



    {{#methods}}
    @Override
    public {{{returnType}}} {{methodName}}({{{parametersSignature}}}) {
    {{#parameters}}
       requireNonNull({{name}}, "{{name}} is required");
    {{/parameters}}
    {{#sourceCode}}
       {{{.}}};
    {{/sourceCode}}
    {{#hasReturn}}
       return {{{returnValue}}};
    {{/hasReturn}}
    }

    {{/methods}}

    private FieldMetadata getIdField() {
        return entityMetadata().id().orElseThrow(KEY_NOT_FOUND_EXCEPTION_SUPPLIER);
    }

    private boolean existsByIdJNoSQL({{keyType}} id) {
        return findById(id).isPresent();
    }

    private Function<{{keyType}}, Stream<{{entityType}}>> optionalToStream() {
        return id -> {
            Optional entity = this.findById(({{keyType}}) id);
            return entity.stream();
        };
    }

    private Class<{{entityType}}> getEntityClass() {
        return (Class<{{entityType}}>) entityMetadata().type();
    }

}
